package org.apache.sad.core.framework.utils;

import java.io.ByteArrayOutputStream;
import java.util.Hashtable;

import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.QueueRequestor;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public abstract class JmsUtils {

	private static final Log logger = LogFactory.getLog(JmsUtils.class);
    
    private final static int DEFAULT_BUFFER_SIZE = 1024 * 10;
    
    // Defines the JNDI context factory - weblogic
    private final static String JNDI_FACTORY="weblogic.jndi.WLInitialContextFactory";
        
    public static byte[] getRawData(BytesMessage bm) throws JMSException {
        byte[] rawBytes = new byte[DEFAULT_BUFFER_SIZE];
        int result = 0;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        do {
            result = bm.readBytes(rawBytes);
            if (result != -1) {
                bos.write(rawBytes, 0, result);
            }
        } while (result != -1);
        return bos.toByteArray();
    }    

    public static Object lookupObject(String name, Class clazz) {

        InitialContext initialcontext = null;
        Object obj = null;
        try {
            initialcontext = new InitialContext();
            obj = initialcontext.lookup(name);
        } catch(Exception e) {
            logger.error("Failed to perform JNDI lookup.", e);           
            //e.printStackTrace();
            throw new RuntimeException("Failed to perform JNDI lookup.");
        } finally {
            if(initialcontext != null) {
                try {
                    initialcontext.close();
                } catch(Exception e) {
                    logger.info("Failed to close initial context.", e);
                }
            }
        }

        if(!clazz.isInstance(obj)) {
        	//TODO
        	//System.out.println("Received invalid object: " + obj.getClass());
            throw new RuntimeException("Received invalid object: " + obj.getClass());
        }
        return obj;
    }


	/**
	 * Close the given JMS Connection and ignore any thrown exception.
	 * This is useful for typical <code>finally</code> blocks in manual JMS code.
	 * @param con the JMS Connection to close (may be <code>null</code>)
	 */
	public static void closeConnection(Connection con) {
		closeConnection(con, false);
	}

	/**
	 * Close the given JMS Connection and ignore any thrown exception.
	 * This is useful for typical <code>finally</code> blocks in manual JMS code.
	 * @param con the JMS Connection to close (may be <code>null</code>)
	 * @param stop whether to call <code>stop()</code> before closing
	 */
	public static void closeConnection(Connection con, boolean stop) {
		if (con != null) {
			try {
				if (stop) {
					try {
						con.stop();
					}
					finally {
						con.close();
					}
				}
				else {
					con.close();
				}
			}
			catch (JMSException ex) {
				logger.debug("Could not close JMS Connection", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JMS provider: It might throw RuntimeException or Error.
                logger.debug("Unexpected exception on closing JMS Connection", ex);
			}
		}
	}

	/**
	 * Close the given JMS Session and ignore any thrown exception.
	 * This is useful for typical <code>finally</code> blocks in manual JMS code.
	 * @param session the JMS Session to close (may be <code>null</code>)
	 */
	public static void closeSession(Session session) {
		if (session != null) {
			try {
				session.close();
			}
			catch (JMSException ex) {
                //logger.debug("Could not close JMS Session", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JMS provider: It might throw RuntimeException or Error.
                logger.debug("Unexpected exception on closing JMS Session", ex);
			}
		}
	}

	/**
	 * Close the given JMS MessageProducer and ignore any thrown exception.
	 * This is useful for typical <code>finally</code> blocks in manual JMS code.
	 * @param producer the JMS MessageProducer to close (may be <code>null</code>)
	 */
	public static void closeMessageProducer(MessageProducer producer) {
		if (producer != null) {
			try {
				producer.close();
			}
			catch (JMSException ex) {
                logger.debug("Could not close JMS MessageProducer", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JMS provider: It might throw RuntimeException or Error.
                logger.debug("Unexpected exception on closing JMS MessageProducer", ex);
			}
		}
	}

	/**
	 * Close the given JMS MessageConsumer and ignore any thrown exception.
	 * This is useful for typical <code>finally</code> blocks in manual JMS code.
	 * @param consumer the JMS MessageConsumer to close (may be <code>null</code>)
	 */
	public static void closeMessageConsumer(MessageConsumer consumer) {
		if (consumer != null) {
			try {
				consumer.close();
			}
			catch (JMSException ex) {
                logger.debug("Could not close JMS MessageConsumer", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JMS provider: It might throw RuntimeException or Error.
                logger.debug("Unexpected exception on closing JMS MessageConsumer", ex);
			}
		}
	}

	/**
	 * Close the given JMS QueueRequestor and ignore any thrown exception.
	 * This is useful for typical <code>finally</code> blocks in manual JMS code.
	 * @param requestor the JMS QueueRequestor to close (may be <code>null</code>)
	 */
	public static void closeQueueRequestor(QueueRequestor requestor) {
		if (requestor != null) {
			try {
				requestor.close();
			}
			catch (JMSException ex) {
                logger.debug("Could not close JMS QueueRequestor", ex);
			}
			catch (Throwable ex) {
				// We don't trust the JMS provider: It might throw RuntimeException or Error.
                logger.debug("Unexpected exception on closing JMS QueueRequestor", ex);
			}
		}
	}

	/**
	 * Commit the Session if not within a JTA transaction.
	 * @param session the JMS Session to commit
	 * @throws JMSException if committing failed
	 */
	public static void commitIfNecessary(Session session) throws JMSException {

        if (session == null) {
            return;
        }

		try {
			session.commit();
		}
		catch (javax.jms.TransactionInProgressException ex) {
			// Ignore -> can only happen in case of a JTA transaction.
		}
		catch (javax.jms.IllegalStateException ex) {
			// Ignore -> can only happen in case of a JTA transaction.
		}
	}

	/**
	 * Rollback the Session if not within a JTA transaction.
	 * @param session the JMS Session to rollback
	 * @throws JMSException if rollback failed
	 */
	public static void rollbackIfNecessary(Session session) {

        if (session == null) {
            return;
        }

		try {
			session.rollback();
		}
		catch (javax.jms.TransactionInProgressException ex) {
			// Ignore -> can only happen in case of a JTA transaction.
		}
		catch (javax.jms.IllegalStateException ex) {
			// Ignore -> can only happen in case of a JTA transaction.
		}
        catch (JMSException e) {
            //duplicated event might be generated
            logger.debug("Failed to rollback JMS local transaction.", e);
        }
	}
    
    public static Object lookupObject(String url, String name) {

        Context context = null;
        Object obj = null;
        try {
            context = getInitialContext(url);
            obj = context.lookup(name);
        } catch(Exception e) {
            logger.error("Failed to perform JNDI lookup.", e);           
            //e.printStackTrace();
            throw new RuntimeException("Failed to perform JNDI lookup.");
        } finally {
            if(context != null) {
                try {
                    context.close();
                } catch(Exception e) {
                    logger.info("Failed to close initial context.", e);
                }
            }
        }
        return obj;
    }    
    
    private static InitialContext getInitialContext(String url) throws NamingException {
        Hashtable<String,String> env = new Hashtable<String,String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
        env.put(Context.PROVIDER_URL, url);
        return new InitialContext(env);
    }    
}
